<?php

namespace App\Service\Auth;

use App\Constants\Rbac\TokenCode;
use App\Constants\Redis\TempRedisCode;
use App\Model\Admin\AdminModel;
use App\Service\BaseService;
use App\Utils\JWT\Exceptions\TokenInvalidException;
use App\Utils\JWT\Payload;
use App\Utils\JWT\Token;
use App\Utils\PrintLogger;
use App\Utils\RateLimiter;
use App\Utils\SecretUtil;
use Hyperf\Di\Annotation\Inject;

/**
 * 后台登录
 */
class AdminAuthService extends BaseService
{

    #[Inject]
    protected AdminModel $adminModel;

    public function pwdLogin(array $data)
    {
        $lockKey = TempRedisCode::adminLogin($data['account']);
        $this->lockError($lockKey);
        $rateKey = TempRedisCode::adminPwdLoginWrongInputKey($data['account']);
        $rater = new RateLimiter($rateKey);
        if ($rater->isLocked()){
            return [false, '允许登录倒计时'. $rater->restLockTime()];
        }
        $admin = $this->adminModel->findByAcc($data['account']);
        if (!$admin){
            $rater->incrCnt();
            $this->freeLock($lockKey);
            return [false,'账号或者密码错误'];
        }
        if (!SecretUtil::verify($data['login_pwd'], $admin->login_pwd)){
            $rater->incrCnt();
            $this->freeLock($lockKey);
            return [false,'账号或者密码错误'];
        }

        $token = $this->makeAdminToken($admin);
        $refreshToken = $this->makeRefreshToken($admin);
        $this->freeLock($lockKey);
        $rater->freeLock();
        return [true,[
            'jwt' => [
                'token' => $token->toString(),
                'exp' => $token->payload->exp,
            ],
            'refresh_token' =>  [
                'token' => $refreshToken->toString(),
                'exp' => $refreshToken->payload->exp,
            ],
        ]];
    }


    public function verifyTokenJti()
    {

    }

    protected function makeRefreshToken(object $acc)
    {
        $payload = new Payload();
        $now = time();
        $ttl = \Hyperf\Config\config('jwt.refresh_ttl', 1296000);
        $payload->exp = $now + $ttl;
        $payload->aud = TokenCode::REFRESH_ADMIN;
        $payload->iat = $now;
        $payload->uid = $acc->id;
        $accUniKey = $this->jtiKeyPre($payload->uid, $payload->aud);
        $payload->jti = md5($accUniKey.':'.$now);

        $jtiKey = TempRedisCode::jtiKey($accUniKey);
        $this->redis->set($jtiKey, $payload->jti,['EX'=>$ttl]);
        return Token::make($payload);
    }

    protected function jtiKeyPre($accId, $aud)
    {
        return $accId . ':' . $aud;
    }

    protected function makeAdminToken(object $acc)
    {
        $payload = new Payload();
        $now = time();
        $payload->exp = $now+7600;
        $payload->aud = TokenCode::LOGIN_ADMIN;
        $payload->iat = $now;
        $payload->uid = $acc->id;
        $accUniKey = $this->jtiKeyPre($payload->uid, $payload->aud);
        $payload->jti = md5($accUniKey.':'.$now);

        $jtiKey = TempRedisCode::jtiKey($accUniKey);
        $this->redis->set($jtiKey, $payload->jti,['EX'=>7600]);
        return Token::make($payload);
    }


    public function refreshToken($refreshToken)
    {
        $lockKey = '';

        try {
            $token = Token::parseRawToken($refreshToken);
            $payload = $token->payload;
            if ($payload->exp < time()){
                throw new TokenInvalidException('已过期');
            }
            if ($payload->aud != TokenCode::REFRESH_ADMIN){
                throw new TokenInvalidException();
            }
            $lockKey = $payload->jti;
            $this->lockError($lockKey);
            $jtiKey = TempRedisCode::jtiKey($this->jtiKeyPre($payload->uid, $payload->aud));
            if ($payload->jti !== $this->redis->get($jtiKey)){
                throw new TokenInvalidException();
            }
            $admin = $this->adminModel->findById($payload->uid,['id',]);
            if (!$admin){
                throw new TokenInvalidException();
            }
            $token = $this->makeAdminToken($admin);
            $refreshToken = $this->makeRefreshToken($admin);
            return [true,[
                'jwt' => [
                    'token' => $token->toString(),
                    'exp' => $token->payload->exp,
                ],
                'refresh_token' =>  [
                    'token' => $refreshToken->toString(),
                    'exp' => $refreshToken->payload->exp,
                ],
            ]];

        }catch (\Throwable $e){
            if($lockKey){
                $this->freeLock($lockKey);
            }
            PrintLogger::dump($e->getMessage());
            return [false,'请重新登录'];
        }
    }

    public function logout($adminId)
    {
        $k1 = TempRedisCode::jtiKey($this->jtiKeyPre($adminId,TokenCode::REFRESH_ADMIN ));
        $k2 = TempRedisCode::jtiKey($this->jtiKeyPre($adminId,TokenCode::LOGIN_ADMIN ));
        $this->redis->del($k1, $k2);
        return [true,'退出成功'];
    }

}